﻿using System;
using System.IO;
using ESRI.ArcGIS.DataSourcesFile;
using ESRI.ArcGIS.DataSourcesGDB;
using ESRI.ArcGIS.Geodatabase;

namespace DllShepherd.Core.Esri.Workspace
{
    public class WorkspaceProvider
    {
        #region Consts

        private const string PersonalGeoDatabaseFileExtension = ".MDB";
        private const string FileGeoDatabaseFileExtension = ".GDB";

        #endregion

        #region Singleton

        [ThreadStatic]
        private static WorkspaceProvider instance;

        // Explicit static constructor to tell C# compiler
        // not to mark type as BeforeFieldInit
        static WorkspaceProvider()
        {
            EsriInitilization.Start();
        }

        WorkspaceProvider()
        {
        }

        public static WorkspaceProvider Instance
        {
            get
            {
                if (instance == null)
                {
                    instance = new WorkspaceProvider();
                    EsriInitilization.Start();
                }

                return instance;
            }
        }

        #endregion Singleton

        #region Basic Cache

        private string _connectionString = String.Empty;
        private WorkspaceUtils _workspaceUtils;
        private IWorkspace _workspace;

        #endregion

        /// <summary>
        /// Get a WorkspaceUtils created from the sdeConnectionString stated in the config
        /// </summary>
        /// <returns></returns>
        public WorkspaceUtils GetWorkspace()
        {
            return HandleWorkspaceCache(DefaultDbConfigSection.Instance.SdeConnectionString);
        }

        /// <summary>
        /// Get a WorkspaceUtils created from the sdeConnectionString parameter
        /// </summary>
        /// <param name="sdeConnectionString"></param>
        /// <returns></returns>
        public WorkspaceUtils GetWorkspace(string sdeConnectionString)
        {
            return HandleWorkspaceCache(sdeConnectionString);
        }

        /// <summary>
        /// Get a Cad WorkspaceUtils for Cad files
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public CadWorkspaceUtils GetCadWorkspace(string path)
        {
            var workspaceFactory = new CadWorkspaceFactory();
            var workspace = (IFeatureWorkspace)workspaceFactory.OpenFromFile(path, 0);
            return new CadWorkspaceUtils(workspace);
        }

        /// <summary>
        /// Get a File WorkspaceUtils for Personal or File GeoDatabase
        /// </summary>
        /// <param name="filePath"></param>
        /// <returns></returns>
        public FileWorkspaceUtils GetFileWorkspace(string filePath)
        {
            var extension = (Path.GetExtension(filePath) ?? String.Empty).ToUpper();
            if (extension.CompareTo(PersonalGeoDatabaseFileExtension) == 0)
                return CreatePersonalGeoDatabaseWorkspace(filePath);
            if (extension.CompareTo(FileGeoDatabaseFileExtension) == 0)
                return CreateFileGeoDatabaseWorkspace(filePath);

            throw new NotImplementedException("The only supported file types are mdb and gdb. Not: " + extension);
        }

        #region Private Methods

        private WorkspaceUtils HandleWorkspaceCache(string sdeConnectionString)
        {
            if (sdeConnectionString.CompareTo(_connectionString) == 0 && _workspace != null && _workspaceUtils != null && _workspaceUtils.IsActive())
            {
                if (!IsWorkspaceRunning(_workspace))
                {
                    ReactivateWorkspace();
                    InitializeWorkspaceUtils(_workspace);
                }
                return _workspaceUtils;
            }
            _connectionString = sdeConnectionString;
            InitializeWorkspaceUtils(CreateWorkspace(sdeConnectionString));

            return _workspaceUtils;
        }

        private void InitializeWorkspaceUtils(IWorkspace workspace)
        {
            _workspace = workspace;
            _workspaceUtils = new WorkspaceUtils((IFeatureWorkspace)_workspace);
        }

        private bool IsWorkspaceRunning(IWorkspace workspace)
        {
            IWorkspaceFactoryStatus workspaceFactory = new SdeWorkspaceFactoryClass();
            return workspaceFactory.PingWorkspaceStatus(workspace).ConnectionStatus == esriWorkspaceConnectionStatus.esriWCSUp && workspace.Exists();
        }

        private void ReactivateWorkspace()
        {
            IWorkspaceFactoryStatus workspaceFactory = new SdeWorkspaceFactoryClass();
            var status = workspaceFactory.PingWorkspaceStatus(_workspace);
            if (status.ConnectionStatus != esriWorkspaceConnectionStatus.esriWCSDown)
            {
                _workspace = workspaceFactory.OpenAvailableWorkspace(status);
            }

            //try to create it again:
            _workspace = CreateWorkspace(_connectionString);
            status = workspaceFactory.PingWorkspaceStatus(_workspace);

            if (status.ConnectionStatus == esriWorkspaceConnectionStatus.esriWCSDown)
            {
                throw new ApplicationException("The Connection to the database was lost. Please contact your system administrators.");
            }
        }

        private IWorkspace CreateWorkspace(string sdeConnectionString)
        {
            IWorkspaceFactory2 workspaceFactory = new SdeWorkspaceFactoryClass();

            EsriInitilization.ReInitialize();

            return workspaceFactory.OpenFromString(sdeConnectionString, 0);
        }

        private FileWorkspaceUtils CreatePersonalGeoDatabaseWorkspace(string filePath)
        {
            var factoryType = Type.GetTypeFromProgID("esriDataSourcesGDB.AccessWorkspaceFactory");
            var workspaceFactory = (IWorkspaceFactory)Activator.CreateInstance(factoryType);

            var workspace = workspaceFactory.OpenFromFile(filePath, 0);
            return new FileWorkspaceUtils((IFeatureWorkspace)workspace, FileGeodatabaseType.Personal);
        }

        private FileWorkspaceUtils CreateFileGeoDatabaseWorkspace(string filePath)
        {
            var factoryType = Type.GetTypeFromProgID("esriDataSourcesGDB.FileGDBWorkspaceFactory");
            var workspaceFactory = (IWorkspaceFactory)Activator.CreateInstance(factoryType);

            var workspace = workspaceFactory.OpenFromFile(filePath, 0);
            return new FileWorkspaceUtils((IFeatureWorkspace)workspace, FileGeodatabaseType.File);
        }

        #endregion
    }
}
